//file:system/lpc/pipe.c
//autor:jiangxinpeng
//time:2021.5.28
//copyright:(C) by jiangxinpeng,All right are reserved.

#include <os/pipe.h>
#include <os/fifo.h>
#include <os/fifobuff.h>
#include <os/fifoio.h>
#include <os/debug.h>
#include <os/task.h>
#include <os/schedule.h>
#include <arch/atomic.h>
#include <lib/list.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <lib/unistd.h>
#include <sys/lpc.h>
#include <sys/ipc.h>

LIST_HEAD(pipe_list_head);

static kobjid_t pipe_id = 0;

pipe_t *CreatePipe()
{
    pipe_t *pipe = KMemAlloc(sizeof(pipe_t));
    if (!pipe)
        return NULL;
    pipe->fifo = FifoBuffAlloc(PIPE_SIZE);
    if (!pipe->fifo)
    {
        KPrint(PRINT_ERR "%s: fifo buffer alloc failed!\n", __func__);
        KMemFree(pipe);
        return NULL;
    }
    //set pipe count
    AtomicSet(&pipe->read_count, 1);
    AtomicSet(&pipe->write_count, 1);
    //init flags
    pipe->flags = 0;
    pipe->rdflags = 0;
    pipe->wrflags = 0;
    pipe->id = pipe_id;
    pipe_id++;
    //init another
    MutexlockInit(&pipe->mutex);
    WaitQueueInit(&pipe->wait_queue);
    list_add_after(&pipe->list, &pipe_list_head);
    return pipe;
}

int DestroyPipe(pipe_t *pipe)
{
    if (!pipe)
    {
        KPrint(PRINT_ERR "%s: fifo object error!\n", __func__);
        return -1;
    }
    list_del(&pipe->list);
    FifoBuffFree(pipe->fifo);
    KMemFree(pipe);
    return 0;
}

pipe_t *PipeFind(kobjid_t id)
{
    pipe_t *pipe;
    list_traversal_all_owner_to_next(pipe, &pipe_list_head, list)
    {
        if (pipe->id == id)
        {
            return pipe;
        }
    }
    return NULL;
}

int PipeRead(kobjid_t id, void *buffer, size_t size)
{
    pipe_t *pipe=NULL;
    int rdsize=0;
    int bytes=size;
    uint8_t *p=buffer;
    int chunk=0;

    if (!buffer || !size)
        return -1;
    pipe = PipeFind(id);
    if (!pipe)
    {
        KPrint(PRINT_ERR "%s: pipe %d not found!\n", __func__, id);
        return -1;
    }
    if (AtomicGet(&pipe->read_count) <= 0)
    {
        KPrint(PRINT_ERR "%s: pipe %d reader no present!\n", __func__, id);
        return -1;
    }

    MutexlockLock(&pipe->mutex, MUTEX_LOCK_MODE_BLOCK);
    while (FifoBuffLen(pipe->fifo) <= 0)
    {
        //writer are closed,return 0
        if (AtomicGet(&pipe->write_count) <= 0)
        {
            MutexlockUnlock(&pipe->mutex);
            return 0;
        }
        if (pipe->rdflags & PIPE_NOWAIT)
        {
            MutexlockUnlock(&pipe->mutex);
            return -1;
        }
        if (ExceptionCauseExit(&cur_task->exception_manager))
        {
            MutexlockUnlock(&pipe->mutex);
            return -1;
        }
      
        WaitQueueAdd(&pipe->wait_queue, cur_task);
        MutexlockUnlock(&pipe->mutex);
        TaskBlock(TASK_BLOCKED);
        MutexlockLock(&pipe->mutex, MUTEX_LOCK_MODE_BLOCK);
    }
    chunk = MIN(size, PIPE_SIZE);
    chunk = MIN(chunk, FifoBuffLen(pipe->fifo));
    chunk = FifoBuffGet(pipe->fifo, buffer, chunk);
    rdsize+=chunk;
    if (AtomicGet(&pipe->write_count) > 0)
    {
        if (WaitQueueLen(&pipe->wait_queue) > 0)
        {
            WaitQueueWakeup(&pipe->wait_queue);
        }
    }
    MutexlockUnlock(&pipe->mutex);
    //KPrint("[pipe] read %d bytes\n",rdsize);
    return rdsize;
}

int PipeWrite(kobjid_t id, void *buffer, size_t size)
{
    pipe_t *pipe=NULL;

    if (!buffer || !size)
        return -1;
    pipe = PipeFind(id);
    if (!pipe)
    {
        KPrint(PRINT_ERR "%s: pipe %d not found!\n", __func__, id);
        return -1;
    }
    if (AtomicGet(&pipe->write_count) <= 0)
    {
        KPrint(PRINT_ERR "%s: pipe %d writer no present!\n", __func__, id);
        return -1;
    }
    //reader are closed,cause exception
    if(AtomicGet(&pipe->read_count)<=0)
    {
        ExceptionForceSelf(EXC_CODE_PIPE);
        return -1;
    }
    MutexlockLock(&pipe->mutex, MUTEX_LOCK_MODE_BLOCK);
    int sizes=size;
    int off=0;
    int chunk=0;
    int wrsize=0;
    uint8_t *buff=buffer;
    while(sizes>0)
    {
        while(FifoBuffAvali(pipe->fifo)<=0)
        {
            if((pipe->wrflags&PIPE_NOWAIT))
            {
                ExceptionCauseExit(&cur_task->exception_manager);
                MutexlockUnlock(&pipe->mutex);
                return -1;
            }
            if(AtomicGet(&pipe->read_count)<=0)
            {
                ExceptionForceSelf(EXC_CODE_PIPE);
                MutexlockUnlock(&pipe->mutex);
                return -1;
            }
          
            WaitQueueAdd(&pipe->wait_queue,cur_task);
            MutexlockUnlock(&pipe->mutex);
            TaskBlock(TASK_BLOCKED);
            MutexlockLock(&pipe->mutex,MUTEX_LOCK_MODE_BLOCK);
        }
        chunk=min(sizes,PIPE_SIZE);
        chunk=min(chunk,FifoBuffAvali(pipe->fifo));
        chunk=FifoBuffPut(pipe->fifo,buff+off,chunk);
        off+=chunk;
        sizes-=chunk;
        wrsize+=chunk;
    }
    if(AtomicGet(&pipe->read_count)>0)
    {
        if(WaitQueueLen(&pipe->wait_queue)>0)
            WaitQueueWakeup(&pipe->wait_queue);
    }
    MutexlockUnlock(&pipe->mutex);
    //KPrint("[pipe] write %d bytes\n",wrsize);
    return wrsize;
}

int PipeClose(kobjid_t id, int rw)
{
    pipe_t *pipe = PipeFind(id);
    if (!pipe)
        return -1;
    if (rw)
    {
        AtomicDec(&pipe->write_count);
    }
    else
    {
        AtomicDec(&pipe->read_count);
    }
    
    if (AtomicGet(&pipe->write_count) <= 0 && AtomicGet(&pipe->read_count) <= 0)
    {
        DestroyPipe(pipe);
    }
    return 0;
}

int PipeClear(pipe_t *pipe)
{
    if(!pipe)
        return -1;
    AtomicSet(&pipe->read_count,1);
    AtomicSet(&pipe->write_count,1);
    pipe->rdflags=0;
    pipe->wrflags=0;
    MutexlockInit(&pipe->mutex);
    WaitQueueInit(&pipe->wait_queue);
    return 0;
}

int PipeIoCtl(kobjid_t id, uint32_t cmd, void *arg, uint32_t rw)
{
    pipe_t *pipe = PipeFind(id);
    if (!pipe)
        return -1;
    MutexlockLock(&pipe->mutex, MUTEX_LOCK_MODE_BLOCK);
    switch (cmd)
    {
    case F_SETFL:
        if ((*(uint32_t *)arg) & O_NONBLOCK)
        {
            if (rw==0)
                pipe->rdflags |= PIPE_NOWAIT;
            else
                pipe->wrflags |= PIPE_NOWAIT;
        }
        break;

    default:
        break;
    }
    MutexlockUnlock(&pipe->mutex);
    return 0;
}

int PipeIncRef(kobjid_t id, int rw)
{
    pipe_t *pipe = PipeFind(id);
    if (!pipe)
        return -1;
    MutexlockLock(&pipe->mutex, MUTEX_LOCK_MODE_BLOCK);
    if (rw)
        AtomicInc(&pipe->write_count);
    else
        AtomicInc(&pipe->read_count);
    MutexlockUnlock(&pipe->mutex);
    return 0;
}

int PipeDecRef(kobjid_t id, int rw)
{
    pipe_t *pipe = PipeFind(id);
    if (!pipe)
        return -1;
    MutexlockLock(&pipe->mutex, MUTEX_LOCK_MODE_BLOCK);
    if (rw)
        AtomicInc(&pipe->write_count);
    else
        AtomicInc(&pipe->read_count);
    MutexlockUnlock(&pipe->mutex);
    return 0;
}

static int PipeIfRdClose(int handle)
{
    return PipeClose(handle, 0);
}

static int PipeIfWrClose(int handle)
{
    return PipeClose(handle, 1);
}

static int PipeIfRdIncRef(int handle)
{
    return PipeIncRef(handle, 0);
}

static int PipeIfRdDecRef(int handle)
{
    return PipeDecRef(handle, 0);
}

static int PipeIfWrIncRef(int handle)
{
    return PipeIncRef(handle, 0);
}

static int PipeIfWrDecRef(int handle)
{
    return PipeDecRef(handle, 0);
}

static int PipeIfRead(int handle, void *buff, size_t size)
{
    return PipeRead(handle, buff, size);
}

static int PipeIfWrite(int handle, void *buff, size_t size)
{
    return PipeWrite(handle, buff, size);
}

static int PipeIfRdIoCtl(int handle, int cmd, void *arg)
{
    return PipeIoCtl(handle, cmd, arg, 0);
}

static int PipeIfWrIoCtl(int handle, int cmd, void *arg)
{
    return PipeIoCtl(handle, cmd, arg, 1);
}

static int PipeIfRdFCtl(int handle, int cmd, void *arg)
{
    return PipeIoCtl(handle, cmd, arg, 0);
}

static int PipeIfWrFCtl(int handle, int cmd, void *arg)
{
    return PipeIoCtl(handle, cmd, arg, 1);
}

fsal_t pipeif_rd = {
    .name = "pipeif_rd",
    .subtable = NULL,
    .mkfs = NULL,
    .mount = NULL,
    .unmount = NULL,
    .open = NULL,
    .close = PipeIfRdClose,
    .read = PipeIfRead,
    .write = NULL,
    .lseek = NULL,
    .opendir = NULL,
    .closedir = NULL,
    .readdir = NULL,
    .mkdir = NULL,
    .unlink = NULL,
    .rename = NULL,
    .ftruncate = NULL,
    .fsync = NULL,
    .status = NULL,
    .chmod = NULL,
    .fchmod = NULL,
    .utime = NULL,
    .feof = NULL,
    .ferror = NULL,
    .ftell = NULL,
    .fsize = NULL,
    .rewind = NULL,
    .rewinddir = NULL,
    .rmdir = NULL,
    .chdir = NULL,
    .ioctl = PipeIfRdIoCtl,
    .fctnl = PipeIfRdFCtl,
    .fstatus = NULL,
    .access = NULL,
    .incref = PipeIfRdIncRef,
    .decref = PipeIfRdDecRef,
    .fastio = NULL,
};

fsal_t pipeif_wr = {
    .name = "pipeif_wr",
    .subtable = NULL,
    .mkfs = NULL,
    .mount = NULL,
    .unmount = NULL,
    .open = NULL,
    .close = PipeIfWrClose,
    .read = NULL,
    .write = PipeIfWrite,
    .lseek = NULL,
    .opendir = NULL,
    .closedir = NULL,
    .readdir = NULL,
    .mkdir = NULL,
    .unlink = NULL,
    .rename = NULL,
    .ftruncate = NULL,
    .fsync = NULL,
    .status = NULL,
    .chmod = NULL,
    .fchmod = NULL,
    .utime = NULL,
    .feof = NULL,
    .ferror = NULL,
    .ftell = NULL,
    .fsize = NULL,
    .rewind = NULL,
    .rewinddir = NULL,
    .rmdir = NULL,
    .chdir = NULL,
    .ioctl = PipeIfWrIoCtl,
    .fctnl = PipeIfWrFCtl,
    .fstatus = NULL,
    .access = NULL,
    .incref = PipeIfWrIncRef,
    .decref = PipeIfWrDecRef,
    .fastio = NULL,
};